/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { ts, ArkFile,AstTreeUtils } from 'arkanalyzer/lib';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import {
  FileMatcher,
  MatcherCallback,
  MatcherTypes,
} from '../../matcher/Matchers';
import { RuleListUtil } from '../../utils/common/DefectsList';
import { Defects, IssueReport } from '../../model/Defects';
import { Rule } from '../../model/Rule';
import { Utils } from '../../utils/common/Utils';
import { RuleFix } from '../../model/Fix';
//methodParams
//结果类型
type Options = [
  'always' | 'never',
  {
    allowNewlines?: boolean;
  }?
];
//always
// 'always',{allowNewlines:true}]
const defaultOptions: Options = ['never']; //默认never

const logger = Logger.getLogger(
  LOG_MODULE_TYPE.HOMECHECK,
  'FuncCallSpacingCheck'
);
const gMetaData: BaseMetaData = {
  severity: 1,
  ruleDocPath: 'docs/func-call-spacing-check.md',
  description: ' Unexpected whitespace between function name and paren.',
};
//要求或不允许函数标识符和它们的调用之间有空格
export class FuncCallSpacingCheck implements BaseChecker {
  readonly metaData: BaseMetaData = gMetaData;
  public rule: Rule;
  public defects: Defects[] = [];
  public issues: IssueReport[] = [];
  private issueMap: Map<string, IssueReport> = new Map();
  private fileMatcher: FileMatcher = {
    matcherType: MatcherTypes.FILE,
  };

  public registerMatchers(): MatcherCallback[] {
    const fileMatchBuildCb: MatcherCallback = {
      matcher: this.fileMatcher,
      callback: this.check,
    };
    return [fileMatchBuildCb];
  }

  public check = (targetFile: ArkFile) => {
    let options = (this.rule.option[0] as Options) || defaultOptions;
    let mergedOptions: Options = [
      options[0] || defaultOptions[0], // 合并第一个部分
      { ...defaultOptions[1], ...options[1] }, // 合并第二个部分，使用 defaultOptions 和 options[1]
    ]; //处理参数
    const sourceFile =  AstTreeUtils.getASTNode(
      targetFile.getName(),
      targetFile.getCode(),
    );
    this.issueMap.clear();
    const sourceFileObject = ts.getParseTreeNode(sourceFile);
    if (sourceFileObject == undefined) {
      return;
    }

    this.loopNode(targetFile, sourceFile, sourceFileObject, mergedOptions);
    this.reportSortedIssues();
  };

  public loopNode(
    targetFile: ArkFile,
    sourceFile: ts.SourceFileLike,
    aNode: ts.Node,
    mergedOptions: Options
  ) {
    const children = aNode.getChildren();
    for (const child of children) {
      if (ts.isCallExpression(child) || ts.isNewExpression(child)) {
        let name: ts.Identifier | undefined = undefined;
        for (const child1 of child.getChildren()) {
          if (ts.isIdentifier(child1)) {
            name = child1;
            break;
          }
        }
        if (name && name?.escapedText) {
          const methodName = name.escapedText;
          const nodeText = child.getText();
          const rangeNum = this.countCharactersAndNewlines(
            nodeText,
            methodName
          ); //获取空格数和换行符数
          const startPosition = ts.getLineAndCharacterOfPosition(
            sourceFile,
            name.getStart()
          ); //获取位置信息
          if (
            mergedOptions.length > 0 &&
            mergedOptions[0] == 'never' &&
            rangeNum[0] > 0
          ) {
            const defect = this.addIssueReport(
              targetFile,
              startPosition.line + 1,
              startPosition.character,
              startPosition.character + methodName.length,
              methodName,
              'Unexpected whitespace between function name and paren.'
            );
            //1.修复含有空格
            const fixText = this.formatFnCall(nodeText, false);
            let fix: RuleFix = this.ruleFix(
              child.getStart(),
              child.getEnd(),
              fixText
            );
            this.issueMap.set(defect.fixKey, { defect, fix });
          } else if (
            mergedOptions.length > 0 &&
            !mergedOptions[1]?.allowNewlines &&
            rangeNum[1] > 0
          ) {
            let message = 'Unexpected newline between function name and paren.';
            const defect = this.addIssueReport(
              targetFile,
              startPosition.line + 1,
              startPosition.character + 1,
              startPosition.character + methodName.length,
              methodName,
              message
            );
            //待确认网页无修复
            const fixText_1 = this.formatFnCall(nodeText, false);
            const fixText = this.formatFnCall(fixText_1, true);
            let fix: RuleFix = this.ruleFix(
              child.getStart(),
              child.getEnd(),
              fixText
            );
            this.issueMap.set(defect.fixKey, { defect, fix });
          } else if (
            mergedOptions.length > 0 &&
            mergedOptions[0] == 'always' &&
            rangeNum[0] == 0
          ) {
            let message = 'Missing space between function name and paren.';
            const defect = this.addIssueReport(
              targetFile,
              startPosition.line + 1,
              startPosition.character + 1,
              startPosition.character + methodName.length - 1,
              methodName,
              message
            );
            const fixText = this.formatFnCall(nodeText, true);
            let fix: RuleFix = this.ruleFix(
              child.getStart(),
              child.getEnd(),
              fixText
            );
            this.issueMap.set(defect.fixKey, { defect, fix });
          }
        }
      }

      if (child.getChildCount() > 0) {
        this.loopNode(targetFile, sourceFile, child, mergedOptions);
      }
    }
  }
  private countCharactersAndNewlines(
    str: string,
    indexstr: string
  ): [charCount: number, newlineCount: number] {
    // 找到 'fn' 和 '(' 之间的部分
    const startIndex = str.indexOf(indexstr) + indexstr.length; // indexstr 结束的位置
    const endIndex = str.indexOf('('); // '(' 的位置

    // 截取 'fn' 和 '(' 之间的子字符串
    const subStr = str.slice(startIndex, endIndex) ?? 0;

    // 计算字符数（包括空格和换行符）
    const charCount = subStr.length ?? 0;

    // 计算换行符的个数
    const newlineCount = (subStr.match(/\n/g) || []).length;

    return [charCount, newlineCount];
  }

  private addIssueReport(
    arkFile: ArkFile,
    line: number,
    startCol: number,
    endCol: number,
    name: string,
    message: string
  ): Defects {
    const severity = this.rule.alert ?? this.metaData.severity;
    const filePath = arkFile.getFilePath();
    const defect = new Defects(
      line,
      endCol,
      endCol,
      message,
      severity,
      this.rule.ruleId,
      filePath,
      this.metaData.ruleDocPath,
      true,
      false,
      false
    );
    this.defects.push(defect);
    return defect;
  }

  //处理格式化问题
  private formatFnCall(code: string, addSpace: boolean): string {
    if (addSpace) {
      // 在函数名和 '(' 之间添加空格，考虑换行符
      return code.replace(/(\w+)\s*\(\s*/g, "$1 (");
    } else {
      // 去除函数名和 '(' 之间的空格和换行符
      return code.replace(/(\w+)\s*\(\s*/g, "$1(");
    }
  }

  private ruleFix(pos: number, end: number, text: string): RuleFix {
    return { range: [pos, end], text: text };
  }

  private reportSortedIssues(): void {
    if (this.issueMap.size === 0) {
      return;
    }

    const sortedIssues = Array.from(this.issueMap.entries()).sort(
      ([keyA], [keyB]) => Utils.sortByLineAndColumn(keyA, keyB)
    );

    this.issues = [];

    sortedIssues.forEach(([_, issue]) => {
      RuleListUtil.push(issue.defect);
      this.issues.push(issue);
    });
  }
}
